home *** CD-ROM | disk | FTP | other *** search
/ Atari Mega Archive 2 / Atari Mega Archive CD - Volume 2.iso / minix / up1510b.tgz / up1510b / src / commands / gather.c < prev    next >
C/C++ Source or Header  |  1990-07-19  |  8KB  |  340 lines

  1. /* gather - collect files for mailing    Author: Andy Tanenbaum */
  2.  
  3. /* It sometimes happens that one needs to mail a large directory full of
  4.  * files to someone.  This program can be used to collect these files into
  5.  * shar archives, compress and uuencode them. The interesting property that
  6.  * it has is that it makes sure that none of the archives are too big, and
  7.  * that no files are split over two archives.
  8.  *
  9.  * Syntax: gather [-s source_dir] [-d dest_dir] [-b max_arch_size] [-f file]
  10.  *
  11.  *    -s source directory    (where are the files to be sent)
  12.  *    -d destination dir    (where should the archives be put)
  13.  *    -b bytes        (maximum size of the archives; default 60K)
  14.  *    -f file            (use file_00.uue etc as archive names)
  15.  *
  16.  * Examples:
  17.  *    gather                # make 60K archives in this dir
  18.  *    gather  -d mailings -b 50000    # make 50K archives in mailings
  19.  *
  20.  * Note:
  21.  *    The maximum size given by -b (default 60000 bytes) is only an
  22.  *    approximation, since it is hard to tell how big the final file
  23.  *    will be after shar'ing, compressing, and uue'ing.  A heuristic
  24.  *    is used.
  25.  */
  26.  
  27. #include <sys/types.h>
  28. #include <sys/stat.h>
  29. #include <dirent.h>
  30. #include <fcntl.h>
  31. #include <stdio.h>
  32.  
  33. #define DEFAULT        60000    /* default archive size */
  34. #define MAX_DIR_ENT      512    /* how many directory entries allowed */
  35. #define HEAP_SIZE      20000    /* storage size for all file names */
  36. #define NAME_SIZE       4096    /* storage size for current command */
  37. #define BASE_SIZE          7    /* max number of chars in basename */
  38. #define PATH_MAX         512    /* largest path name */
  39. #define NUMERATOR       138L    /* heuristic parameter */
  40. #define DENOMINATOR     100L    /* heuristic parameter */
  41.  
  42. char heap[HEAP_SIZE + 2];    /* dir entries stored here */
  43. char names[NAME_SIZE];        /* file name lists constructed here */
  44. char work[NAME_SIZE];        /* scratch buffer */
  45. char base_name[BASE_SIZE + 1];    /* base name to use for the archives */
  46. char target[PATH_MAX];        /* storage for target file names */
  47.  
  48. struct dir_ent {
  49.   char *file_name;
  50.   long file_size;
  51. } dir_ent[MAX_DIR_ENT];
  52.  
  53. long atol(), heuristic();
  54. char *getcwd();
  55.  
  56. main(argc, argv)
  57. int argc;
  58. char *argv[];
  59. {
  60. /* Parse the command and get ready. */
  61.  
  62.   int i, counter, l, s, nonlocal;
  63.   char *p, num[3];
  64.   struct dirent *d;
  65.   DIR *dirp;
  66.   struct stat stbuf;
  67.   int first;            /* first entry not used yet */
  68.   int limit;            /* number of files in src_dir */
  69.   char *src_dir = ".";        /* pointer to source directory */
  70.   char *dst_dir = ".";        /* pointer to destination directory */
  71.   char *file = "";        /* name to use */
  72.   long max_bytes = DEFAULT;    /* max archive size (approx.) */
  73.   long cutoff;            /* max cumulative input size */
  74.  
  75.   if (argc > 9) usage();
  76.   i = 1;
  77.   while (i < argc) {
  78.     /* Examine the i-th argument. */
  79.     p = argv[i];
  80.     if (*p != '-') usage();
  81.     switch (*(p + 1)) {
  82.         case 's':    src_dir = argv[i + 1];    break;
  83.         case 'd':    dst_dir = argv[i + 1];    break;
  84.         case 'f':    file = argv[i + 1];    break;
  85.  
  86.         case 'b':
  87.         max_bytes = atol(argv[i + 1]);
  88.         if (max_bytes <= 0) {
  89.             fprintf(stderr, "gather: bad -b value\n");
  90.             exit(1);
  91.         }
  92.         break;
  93.  
  94.         default:
  95.         fprintf(stderr, "gather: unknown flag %s\n", p);
  96.         exit(1);
  97.     }
  98.     i += 2;
  99.   }
  100.  
  101.   /* Determine the basename. */
  102.   get_basename(src_dir, file);
  103.  
  104.   /* Open the source directory. */
  105.   i = 0;
  106.   p = heap;
  107.   if ((dirp = opendir(src_dir)) == (DIR *) NULL) {
  108.     fprintf(stderr, "gather: cannot open %s\n", src_dir);
  109.     exit(2);
  110.   }
  111.  
  112.   /* Read in all the file names. */
  113.   while (1) {
  114.     d = readdir(dirp);
  115.     if (d == (struct dirent *) NULL) break;
  116.     l = strlen(d->d_name);
  117.     if (p + l >= &heap[HEAP_SIZE] || i >= MAX_DIR_ENT) {
  118.         fprintf(stderr, "gather: %s is too large\n", src_dir);
  119.         exit(2);
  120.     }
  121.     strcpy(work, src_dir);
  122.     strcat(work, "/");
  123.     strcat(work, d->d_name);
  124.     stat(work, &stbuf);
  125.     if ((stbuf.st_mode & S_IFMT) == S_IFDIR) continue;
  126.     dir_ent[i].file_name = p;
  127.     strcpy(p, d->d_name);
  128.     dir_ent[i].file_size = stbuf.st_size;
  129.     p += l + 1;
  130.     i++;
  131.   }
  132.   limit = i;
  133.   closedir(dirp);
  134.  
  135.   /* Sort the names. */
  136.   sort_dir(limit);
  137.  
  138.   /* Figure out when to stop reading files. */
  139.   cutoff = heuristic(max_bytes);
  140.  
  141.   /* Collect files into archives. */
  142.   first = 0;
  143.   counter = 0;
  144.   while (first < limit) {
  145.     first = collect(first, limit, cutoff);
  146.     num[0] = '0' + (counter / 10);
  147.     num[1] = '0' + (counter % 10);
  148.     num[2] = 0;
  149.  
  150.     /* Construct full path of compressed target. */
  151.     target[0] = 0;
  152.     if (strcmp(dst_dir, ".") != 0) {
  153.         strcpy(target, dst_dir);
  154.         strcat(target, "/");
  155.     }
  156.     strcat(target, base_name);
  157.     strcat(target, "_");
  158.     strcat(target, num);
  159.     strcat(target, ".Z");
  160.  
  161.     /* (cd src; shar file ... | compress -fc) >dir/base.00.Z */
  162.     nonlocal = strcmp(src_dir, ".");
  163.     work[0] = 0;
  164.     if (nonlocal) {
  165.         strcat(work, "(cd ");
  166.         strcat(work, src_dir);
  167.         strcat(work, "; ");
  168.     }
  169.     strcat(work, "shar ");
  170.     strcat(work, names);
  171.     strcat(work, " | compress -fc ");
  172.     if (nonlocal) strcat(work, ")");
  173.     strcat(work, " >");
  174.     strcat(work, target);
  175.     s = system(work);
  176.     if (s < 0) {
  177.         fprintf(stderr, "gather: shar command failed\n");
  178.         exit(2);
  179.     }
  180.  
  181.     /* Uue dir/base.00.Z */
  182.     strcpy(work, "uue ");
  183.     strcat(work, target);
  184.     strcat(work, "\n");
  185.     s = system(work);
  186.     if (s < 0) {
  187.         fprintf(stderr, "gather: uue command failed\n");
  188.         exit(2);
  189.     }
  190.  
  191.     /* Unlink dir/base.00.Z */
  192.     unlink(target);
  193.  
  194.     counter++;
  195.   }
  196. }
  197.  
  198. int collect(first, limit, cutoff)
  199. int first;
  200. int limit;
  201. long cutoff;
  202. {
  203. /* See how many files will fit in an archive. */
  204.  
  205.   int nr_files;
  206.   long cum_size, size;
  207.   struct dir_ent *p, *endp;
  208.  
  209.   names[0] = 0;
  210.   p = &dir_ent[first];
  211.   endp = &dir_ent[limit];
  212.   nr_files = 0;
  213.   cum_size = 0;
  214.  
  215.   while (p < endp) {
  216.     size = p->file_size;
  217.     if (size > cutoff) {
  218.         fprintf(stderr, "gather: %s is too big\n", p->file_name);
  219.         exit(2);
  220.     }
  221.  
  222.     /* First peek to see if next file fits.  If not, maybe some
  223.      * other file can be used instead.  Swap them. */
  224.     if (cum_size + size > cutoff) fudge(p, endp, cutoff - cum_size);
  225.  
  226.     /* If it fails now, there is no file that will fit. */
  227.     size = p->file_size;
  228.     if (cum_size + size > cutoff) return(p - dir_ent);
  229.     strcat(names, p->file_name);
  230.     strcat(names, " ");
  231.     cum_size += size;
  232.     p++;
  233.   }
  234.   return(p - dir_ent);
  235. }
  236.  
  237. long heuristic(m)
  238. long m;
  239. {
  240.   /* The basic algorithm is to collect files up to some limit, and put
  241.    * them in an archive.  It is tricky to determine how many files to
  242.    * collect, because they will be shar'ed, compressed and uue'ed.
  243.    * Thus we need a heuristic for guessing how to relate the total size
  244.    * of the input files to the size of the final uue archive.  This
  245.    * heuristic is contained in this procedure.  It takes the desired
  246.    * final size as input and produces the file cutoff as output. */
  247.  
  248.   return((NUMERATOR * m) / DENOMINATOR);
  249. }
  250.  
  251.  
  252.  
  253. sort_dir(limit)
  254. int limit;            /* how many entries in dir_ent */
  255. {
  256. /* Sort the directory using bubble sort. */
  257.  
  258.   struct dir_ent *p, *q;
  259.  
  260.   for (p = &dir_ent[0]; p < &dir_ent[limit - 1]; p++) {
  261.     for (q = p + 1; q < &dir_ent[limit]; q++) {
  262.         if (strcmp(p->file_name, q->file_name) > 0) swap(p, q);
  263.     }
  264.   }
  265. }
  266.  
  267. swap(p, q)
  268. struct dir_ent *p, *q;
  269. {
  270.   /* Exchange two entries. */
  271.  
  272.   char *cp;
  273.   long l;
  274.  
  275.   cp = p->file_name;
  276.   l = p->file_size;
  277.   p->file_name = q->file_name;
  278.   p->file_size = q->file_size;
  279.   q->file_name = cp;
  280.   q->file_size = l;
  281. }
  282.  
  283.  
  284. fudge(p, endp, size)
  285. struct dir_ent *p, *endp;
  286. long size;
  287. {
  288. /* Look for a file that will fit (i.e., <= size). This fudging gives a more
  289.  * uniform distribution, and reduces the number of files needed.
  290.  */
  291.  
  292.   register struct dir_ent *q;
  293.  
  294.   for (q = p + 1; q < endp; q++) {
  295.     if (q->file_size <= size) {
  296.         swap(p, q);
  297.         return;
  298.     }
  299.   }
  300. }
  301.  
  302.  
  303. get_basename(s, file)
  304. char *s;
  305. char *file;
  306. {
  307. /* Determine the basename and copy it to base_name. */
  308.  
  309.   int fd, n;
  310.   char *p, *q;
  311.  
  312.   if (*file != 0) p = file;
  313.   else if (strcmp(s, ".") == 0) {
  314.     if (getcwd(work, NAME_SIZE) == (char *) NULL) {
  315.         fprintf(stderr, "gather: could not get name of working dir\n");
  316.         exit(2);
  317.     }
  318.     p = work;
  319.   } else {
  320.     p = s;
  321.   }
  322.  
  323.   q = p + strlen(p) - 1;
  324.   if (*q == '\n') {
  325.     *q = 0;
  326.     q--;
  327.   }
  328.   while (1) {
  329.     if (q < p || *q == '/') break;
  330.     q--;
  331.   }
  332.   strncpy(base_name, q + 1, BASE_SIZE);
  333. }
  334.  
  335. usage()
  336. {
  337.   fprintf(stderr, "Usage: gather [-b bytes] [-s src_dir] [-d dst_dir] [-f file]\n");
  338.   exit(1);
  339. }
  340.